/**
 * #info
 *
 *@author   wuwg
 *@createTime   2018/03/20
 *@updateTime   2019/12/17
 *@description  upload  component  上传组件
 */

/**
 * #item
 *
 * @type        aty-upload
 * @cn          上传
 * @nameprop   label
 * @menu        表单
 * @stPlugin    true
 * @stContainer false
 * @children    [aty-button]
 * @mixins      [./components/components/form/form-item/form-item.js]
 */

import config from '../../../config/config.js'
// components
import UploadList from './upload-list/index.vue'
import AtyButton from '../../basic/button/index.vue'
// mixins
import formItem from '../form-item/form-item.js'
// prefix
const prefixCls = config.prefix + '-upload'
// module
export default {
  name: config.prefix + '-upload',
  mixins: [formItem],
  components: { UploadList, AtyButton },
  props: {
    /**
     * #property
     *
     * @name     name
     * @cn      属性名称
     * @group   基本属性
     * @type    string
     * @value  file
     * @description 值对应的属性名, 上传给后台的key
     */
    name: {
      type: String,
      default: 'file'
    },
    /**
     * #property
     *
     * @name     action
     * @cn      上传的地址
     * @group   基本属性
     * @type    string
     * @required true
     * @description 上传的地址，必填
     */
    action: {
      type: String,
      required: true
    },
    /**
     * #property
     *
     * @name     headers
     * @cn      设置上传的请求头部
     * @group   基本属性
     * @type    object
     * @bind    must
     * @description 设置上传的请求头部
     */
    headers: {
      type: Object,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name     multiple
     * @cn      是否支持多选文件
     * @group   控制
     * @type    boolean
     * @value  false
     * @description 是否支持多选文件
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name     data
     * @cn      上传时附带的额外参数
     * @group   参数
     * @type    object
     * @bind    must
     * @description 上传时附带的额外参数
     */
    data: {
      type: Object
    },

    /**
     * #property
     *
     * @name     withCredentials
     * @cn      支持发送 cookie 凭证信息
     * @group   控制
     * @type    boolean
     * @value  false
     * @description  是否支持发送 cookie 凭证信息
     */
    withCredentials: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name     showUploadList
     * @cn      是否显示已上传文件列表
     * @group   控制
     * @type    boolean
     * @value  true
     * @bind  must
     * @description  是否显示已上传文件列表
     */
    showUploadList: {
      type: Boolean,
      default: true
    },
    /**
     * #property
     *
     * @name     type
     * @cn      上传控件的类型
     * @group   基本属性
     * @type    list
     * @options [select, drag]
     * @value    select
     * @description  上传控件的类型，取值为 ['select', 'drag']中的其中一个,select（点击选择），drag（支持拖拽）
     */
    type: {
      type: String,
      validator (value) {
        return Artery.isOneOf(value, ['select', 'drag'])
      },
      default: 'select'
    },
    /**
     * #property
     *
     * @name     accept
     * @cn      接受上传的文件类型
     * @group   基本属性
     * @type    string
     * @description  接受上传的文件类型
     */
    accept: {
      type: String
    },
    /**
     * #property
     *
     * @name     format
     * @cn      支持的文件类型
     * @group   基本属性
     * @type    array
     * @bind    must
     * @description  数组类型，
     * 与 accept 不同的是，format 是识别文件的后缀名，
     * accept 为 input 标签原生的 accept 属性，会在选择文件时过滤，可以两者结合使用
     */
    format: {
      type: Array,
      default () {
        return []
      }
    },
    /**
     * #property
     *
     * @name     maxSize
     * @cn      文件大小限制，单位 kb
     * @group   基本属性
     * @type    number
     * @bind    must
     * @description  文件大小限制，单位 kb
     */
    maxSize: {
      type: Number
    },
    /**
     * #property
     *
     * @name    elementId
     * @cn      input id
     * @group   基本属性
     * @type    string
     * @description 该输入框上的id
     */
    elementId: {
      type: String
    },
    /**
     * #property
     *
     * @name    beforeUpload
     * @cn      上传文件之前的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 上传文件之前的钩子，参数为上传的文件，若返回 false 或者 Promise 则停止上传
     */
    beforeUpload: Function,
    /**
     * #property
     *
     * @name    onProgress
     * @cn      文件上传时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件上传时的钩子，返回字段为 event, file, fileList
     */
    onProgress: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    onSuccess
     * @cn      文件上传成功时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件上传成功时的钩子，返回字段为 response, file, fileList
     */
    onSuccess: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    onError
     * @cn      文件上传成功时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件上传失败时的钩子，返回字段为 error, file, fileList
     */
    onError: {
      type: Function,
      default () {
        return {}
      }
    },

    /**
     * #property
     *
     * @name    onPreview
     * @cn      点击已上传的文件链接时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 点击已上传的文件链接时的钩子，返回字段为 file， 可以通过 file.response 拿到服务端返回数据
     */
    onPreview: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    onRemove
     * @cn      文件列表移除文件时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件列表移除文件时的钩子，返回字段为 file, fileList
     */
    onRemove: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    onExceededSize
     * @cn      文件超出指定大小限制时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件超出指定大小限制时的钩子，返回字段为 file, fileList
     */
    onExceededSize: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    onFormatError
     * @cn      文件格式验证失败时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description  文件格式验证失败时的钩子，返回字段为 file, fileList
     */
    onFormatError: {
      type: Function,
      default () {
        return {}
      }
    },
    /**
     * #property
     *
     * @name    defaultFileList
     * @cn      默认文件列表
     * @group   数据
     * @type    array
     * @bind    must
     * @description  默认已上传的文件列表，例如：
     * [
     * {
     * name: 'img1.jpg',
     * url: 'http://www.xxx.com/img1.jpg'
     * },
     * {
     * name: 'img2.jpg',
     * url: 'http://www.xxx.com/img2.jpg'
     * }
     * ]
     */
    defaultFileList: {
      type: Array,
      default () {
        return []
      }
    },
    /**
     * #property
     *
     * @name    submitWithForm
     * @cn      跟随表单提交
     * @group   表单提交
     * @type    boolean
     * @value  false
     * @description 是否跟随表单一起提交, 默认false
     */
    submitWithForm: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    submitParamName
     * @cn      后台提交文件的参数名称
     * @group   表单提交
     * @type    string
     * @value uploadFiles
     * @description 后台接收提交文件的参数名称(用MultipartFile[]接收)
     */
    submitParamName: {
      type: String,
      default: 'uploadFiles'
    },
    /**
     * #property
     *
     * @name    limit
     * @cn      最大允许上传个数
     * @group   基本属性
     * @type    number
     * @bind    must
     * @description  最大允许上传个数
     */
    limit: {
      type: Number
    },
    /**
     * #property
     *
     * @name    onExceed
     * @cn      文件超出个数限制时的钩子
     * @group   钩子
     * @type    function
     * @bind    must
     * @description 文件超出个数限制时的钩子，参数为当前选中的文件files和已存在的文件列表fileList
     */
    onExceed: Function
  },
  data () {
    return {
      prefixCls: prefixCls,
      dragOver: false,
      fileList: [],
      tempIndex: 1,
      submitFormData: {},
      inputKey: 0 // 修改IE10人神共愤的上传问题
    }
  },
  watch: {
    defaultFileList: {
      immediate: true,
      handler (fileList) {
        this.fileList = fileList.map(item => {
          item.status = 'finished'
          item.percentage = 100
          item.uid = Date.now() + this.tempIndex++
          return item
        })
      }
    }
  },
  computed: {
    wrapClasses () {
      return [
        `${prefixCls}`
      ].concat(this.formItemClasses)
    },
    classes () {
      return [
                `${prefixCls}`,
                {
                  [`${prefixCls}-select`]: this.type === 'select',
                  [`${prefixCls}-drag`]: this.type === 'drag',
                  [`${prefixCls}-dragOver`]: this.type === 'drag' && this.dragOver
                }
      ]
    }

  },
  methods: {
    getValidateValue() {
      if (this.submitWithForm) {
        const files = Object.values(this.submitFormData)
        return files.length == 0 ? null : files
      } else {
        return this.fileList.length == 0 ? null : this.fileList
      }
    },
    handleClick () {
      this.$refs.input.click()
    },
    handleChange (e) {
      const files = e.target.files
      if (!files) {
        return
      }
      // 上传文件列表
      this.uploadFiles(files)
      // 清空值
      if (this.isIE10) {
        this.inputKey++
      } else {
        this.$refs.input.value = null
      }
    },
    // 拖拽文件
    onDrop (e) {
      this.dragOver = false
      // 上传文件列表
      this.uploadFiles(e.dataTransfer.files)
    },
    /**
     * #method
     *
     * @name   uploadFiles
     * @param   files ｛Array｝ 文件数组
     * @description  手动上传文件
     */
    uploadFiles  (files) {
      const limit = this.multiple ? this.limit : 1
      if (limit && this.fileList.length + files.length > limit) {
        this.onExceed && this.onExceed(files, this.fileList)
        return
      }

      let postFiles = Array.prototype.slice.call(files)
      if (!this.multiple) {
        postFiles = postFiles.slice(0, 1)
      }

      if (postFiles.length === 0) return

      postFiles.forEach(file => {
        this.upload(file)
      })
    },
    /**
     * #method
     *
     * @name   upload
     * @param   file ｛File｝ 文件
     * @description  手动上传文件
     */
    upload (file) {
      // 提交表单参数处理
      if (this.submitWithForm) {
        const random = Math.floor(Math.random() * 1000 + 1)
        this.submitFormData[this.submitParamName + '_' + new Date().getTime() + '_' + random] = file
        if (this.beforeUpload) {
          this.beforeUpload(file)
        }
        if (this.validateState) {
          this.validate()
        }
        return
      }

      if (!this.beforeUpload) {
        return this.post(file)
      }

      const before = this.beforeUpload(file)
      if (before && before.then) {
        before.then(processedFile => {
          if (Object.prototype.toString.call(processedFile) === '[object File]') {
            this.post(processedFile)
          } else {
            this.post(file)
          }
        })
      } else if (before !== false) {
        this.post(file)
      }
    },
    // ajax
    post (file) {
      // check format
      if (this.format.length) {
        const _file_format = file.name.split('.').pop().toLocaleLowerCase()
        const checked = this.format.some(item => item.toLocaleLowerCase() === _file_format)
        if (!checked) {
          this.onFormatError(file, this.fileList)
          return false
        }
      }

      // check maxSize
      if (this.maxSize) {
        if (file.size > this.maxSize * 1024) {
          this.onExceededSize(file, this.fileList)
          return false
        }
      }

      this.handleStart(file)
      const formData = new FormData()
      formData.append(this.name, file)
      if (this.data) {
        Object.keys(this.data).map(key => {
          formData.append(key, this.data[key])
        })
      }
      const headers = this.headers || {}
      headers['Content-Type'] = 'multipart/form-data'
      let _this = this
      // 使用axios代替自己封装的ajax
      Artery.ajax.post(this.action, formData, {
        headers: headers,
        timeout: 0, // 上传默认不超时
        withCredentials: this.withCredentials,
        onUploadProgress: e => this.handleProgress(e, file)
      }).then(result => {
        _this.handleSuccess(result, file)
        if (_this.validateState) {
          _this.validate()
        }
      }).catch(err => {
        _this.handleError(err, '', file)
        if (_this.validateState) {
          _this.validate()
        }
      })
    },
    handleStart (file) {
      file.uid = Date.now() + this.tempIndex++
      const _file = {
        status: 'uploading',
        name: file.name,
        size: file.size,
        percentage: 0,
        uid: file.uid,
        showProgress: true
      }

      this.fileList.push(_file)
    },
    getFile (file) {
      const fileList = this.fileList
      let target
      fileList.every(item => {
        target = file.uid === item.uid ? item : null
        return !target
      })
      return target
    },
    handleProgress (e, file) {
      if (e.total > 0) {
        e.percent = e.loaded / e.total * 100
      }
      const _file = this.getFile(file)
      this.onProgress(e, _file, this.fileList)
      _file.percentage = e.percent || 0
    },
    handleSuccess (res, file) {
      const _file = this.getFile(file)

      if (_file) {
        _file.status = 'finished'
        _file.response = res

        this.onSuccess(res, _file, this.fileList)

        setTimeout(() => {
          _file.showProgress = false
        }, 1000)
      }
    },
    handleError (err, response, file) {
      const _file = this.getFile(file)
      const fileList = this.fileList

      _file.status = 'fail'

      fileList.splice(fileList.indexOf(_file), 1)

      this.onError(err, response, file)
    },
    handleRemove (file) {
      const fileList = this.fileList
      fileList.splice(fileList.indexOf(file), 1)
      this.onRemove(file, fileList)
    },
    handlePreview (file) {
      if (file.status === 'finished') {
        this.onPreview(file)
      }
    },
    /**
     * #method
     *
     * @name   removeFile
     * @param   file ｛File｝ 文件
     * @description  删除上传列表中的文件
     */
    removeFile (file) {
      if (this.submitFormData) {
        for (const k in this.submitFormData) {
          if (this.submitFormData[k] === file) {
            delete this.submitFormData[k]
          }
        }
      } else {
        this.fileList.splice(this.fileList.indexOf(file), 1)
      }
    },
    /**
     * #method
     *
     * @name   clearFiles
     * @description  清空上传列表中的文件
     */
    clearFiles () {
      this.fileList = []
      if (this.submitFormData) {
        for (const k in this.submitFormData) {
          if (Artery.type(this.submitFormData[k]) === 'file') {
            delete this.submitFormData[k]
          }
        }
      }
    }
  },
  created () {
    // 提交表单参数处理
    if (this.submitWithForm) {
      let parent = this.$parent
      while (parent && parent.$options.name != 'aty-form') {
        parent = parent.$parent
      }
      if (parent && parent.$options.name == 'aty-form') {
        parent.multipartSubmit = true
        parent.getData(true, (data) => {
          if (data) {
            this.submitFormData = data
          }
        })
        // 监听表单data变化
        this.$on('update-data', newData => {
          this.submitFormData = newData
        })
      }
    }
    if (!Artery.isServer) {
    this.isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1
    }
  },
}
